package game;

import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import firing.FiringTree;
import gp.TreeGenerator;

public class GameBoard {
    //how many frames pass between decision tree calls
    public static final int FIRING_FREQUENCY = 3;
    
    public Player player;
    public Enemy enemy;
    public List<Bullet> enemyBullets;
    public List<Bullet> playerBullets;
    Dimension boardSize = new Dimension(400,600);
    public Boundary boardBounds = new Boundary(0, 0, 400, 600);
    private int framesSinceFired;
    //tracks total damage taken
    public int playerDamage;
    public int enemyDamage;
    public int enemyBulletsFired;
    public int nearHits;
    public int bulletProximityPenalty;
    public boolean[] keyState;
    public FiringTree overmind;
    public boolean showChainPenaltyRadius;
    //determines if the player's hitbox is 1 pixel (true) or their entire circle (false)
    private boolean pixelHitBox;
    //Determines if autopilot should attempt to avoid bullets
    private boolean advancedPlayerAI;
    
    public static final int UP = KeyEvent.VK_UP;
    public static final int DOWN = KeyEvent.VK_DOWN;
    public static final int LEFT = KeyEvent.VK_LEFT;
    public static final int RIGHT = KeyEvent.VK_RIGHT;
    public static final int SPACE = KeyEvent.VK_SPACE;

    public static final double NEAR_MISS_RADIUS = 100;
    public static final double BULLET_CHAIN_RADIUS = 35;
    public static final double BULLET_PENALTY_RADIUS = 25;
    
    public GameBoard(boolean[] keyState) {
	showChainPenaltyRadius = false;
	enemyBullets = new LinkedList<Bullet>();
	playerBullets = new LinkedList<Bullet>();
	player = new Player(200, 500);
	enemy = new Enemy(100, 100, new Boundary(0,0,400,300));
	
	//sets  bounds for all bullets
	Bullet b = new Bullet(0, 0, 0, 0);
	b.setBounds(new Boundary(0, 0, 400, 600));
	playerDamage = 0;
	enemyDamage = 0;
	enemyBulletsFired = 0;
	framesSinceFired = 0;
	nearHits = 0;
	bulletProximityPenalty = 0;
	this.keyState = keyState;
	//overmind = new FiringTree();
	overmind = TreeGenerator.generateTree();
	
	//autopilot testing
	//player.setAutoPilot(true);
	setPlayerAutoPilot(true);
	pixelHitBox = false;
	
    }

    public void rebuildTree() {
	setTree(TreeGenerator.generateTree());
    }
    
    public void setTree(FiringTree t) {
	enemyBullets.clear();
	playerDamage = 0;
	enemyBulletsFired = 0;
	overmind = t;
	nearHits = 0;
	bulletProximityPenalty = 0;
    }
    
    public void update() {
	//check player movement
	interpretPlayerKeys();
	if (advancedPlayerAI) {
	    player.update(enemyBullets);
	} else {
	    player.update();
	}
	enemy.update();
	
	framesSinceFired++;
	if (framesSinceFired > FIRING_FREQUENCY) {
	    overmind.fire(player, enemy, enemyBullets, player);
	    framesSinceFired = 0;
	}
	
	
	//check collision and out of bounds on enemy bullets
	Iterator<Bullet> i = enemyBullets.iterator();
	while (i.hasNext()){
	    Bullet b = i.next();
	    b.update();
	    if (!b.nearMiss && getDistanceSquared(player, b) < Math.pow(NEAR_MISS_RADIUS, 2)) {
		b.nearMiss = true;
		nearHits++;
		for(Bullet nearby:enemyBullets) {
		    if (getDistanceSquared(b,nearby) < Math.pow(BULLET_CHAIN_RADIUS,2)) {
			nearby.nearMiss = true; //prevent straight lines of bullets from being rewarding
		    }
		    if (getDistanceSquared(b,nearby) < Math.pow(BULLET_PENALTY_RADIUS, 2)) {
			bulletProximityPenalty++;
		    }
		}
	    }
	    if (getDistanceSquared(player, b) < Math.pow(Bullet.collisionRadius[b.size] + (pixelHitBox?0:player.getCollisionRadius()),2)) {
		playerDamage++;
		i.remove();
		enemyBulletsFired++;
		continue; //prevents crashing if bullet is out of bounds AND hits player
	    }
	    if (boardBounds.isOutOfBounds(b.getX(), b.getY())) {
		i.remove();
		enemyBulletsFired++;
	    }
	}
	
	//check collision and out of bounds on player bullets
	i = playerBullets.iterator();
	while (i.hasNext()){
	    Bullet b = i.next();
	    b.update();
	    if (getDistanceSquared(enemy, b) < Math.pow(Bullet.collisionRadius[b.size]+ enemy.getCollisionRadius(),2)) {
		enemyDamage++;
		i.remove();
		continue;//prevents crashing if bullet out of bounds
	    }
	    if (boardBounds.isOutOfBounds(b.getX(), b.getY())) {
		i.remove();
	    }
	}
	
    }
    
    public void togglePixelHitBox() {
	pixelHitBox = !pixelHitBox;
    }
    
    public boolean isPixelHitBox() {
	return pixelHitBox;
    }
    
    public void setPlayerAutoPilot(boolean autoPilot) {
	if (autoPilot) {
	    player.setBounds(new Boundary(0, 200, 400, 600));
	    player.setX(200);
	    player.setY(500);
	} else {
	    player.setBounds(boardBounds);
	}
	player.setAutoPilot(autoPilot);
    }
    
    private void interpretPlayerKeys() {
	player.setVelocity(Player.DEFAULT_VELOCITY);
	if (player.isAutoPilot()) {
	    return;
	}
	if (keyState[RIGHT] && !keyState[LEFT] && !(keyState[UP] ^ keyState[DOWN])) {
	    player.setAngle(0); 
	} else if (keyState[RIGHT] && keyState[UP] && !(keyState[LEFT] || keyState[DOWN])) {
	    player.setAngle(Math.PI*7/4);
	} else if (keyState[UP] && !keyState[DOWN] && !(keyState[LEFT] ^ keyState[RIGHT])) {
	    player.setAngle(Math.PI*3/2);
	} else if (keyState[LEFT] && keyState[UP] && !(keyState[RIGHT] || keyState[DOWN])) {
	    player.setAngle(Math.PI*5/4);
	} else if (keyState[LEFT] && !keyState[RIGHT] && !(keyState[UP] ^ keyState[DOWN])) {
	    player.setAngle(Math.PI);
	} else if (keyState[LEFT] && keyState[DOWN] && !(keyState[UP] || keyState[RIGHT])) {
	    player.setAngle(Math.PI*3/4);
	} else if (keyState[DOWN] && !keyState[UP] && !(keyState[LEFT] ^ keyState[RIGHT])) {
	    player.setAngle(Math.PI/2);
	} else if (keyState[RIGHT] && keyState[DOWN] && !(keyState[UP] || keyState[LEFT])) {
	    player.setAngle(Math.PI/4);
	} else {
	    player.setVelocity(0);
	}
	if (keyState[SPACE]) {
	    if (player.canFire()) {
		playerBullets.add(new Bullet(player.getX(), player.getY(), Math.PI/2*3, Bullet.PLAYER));
		playerBullets.add(new Bullet(player.getX(), player.getY(), Math.PI/2*3 + .2, Bullet.PLAYER));
		playerBullets.add(new Bullet(player.getX(), player.getY(), Math.PI/2*3 - .2, Bullet.PLAYER));
		player.updateLastFired(true);
	    } else {
		player.updateLastFired(false);
	    }
	}
	
    }

    public static int getDistanceSquared(Entity e, Bullet b) {
	return (int) (Math.pow(e.getX() - b.getX(),2)) + (int) (Math.pow(e.getY() - b.getY(), 2));
    }

    public void togglePlayerAI() {
	advancedPlayerAI = !advancedPlayerAI;
    }
    
    public boolean isAdvancedPlayerAI() {
	return advancedPlayerAI;
    }
    
    public void setAdvancedPlayerAI(boolean b) {
	advancedPlayerAI = b;
    }
    
}
